package hit.reports;

import gui.product.ProductData;
import hit.core.HomeInventoryTracker;
import hit.core.Item;
import hit.core.ItemIndex;
import hit.core.Product;
import hit.core.ProductGroup;
import hit.core.ProductIndex;
import hit.core.StorageUnit;

import java.text.DecimalFormat;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.Map;
import java.util.HashMap;
import java.util.List;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.PriorityQueue;
import java.util.Comparator;

/**
 * Builds the report of the products statistics
 *
 */
public class ProductStatisticsReport implements ReportDesigner, Visitor {
	
	public class ItemEvent
	{
		public Date date;
		public boolean added;
		
		public ItemEvent(Date _date, boolean _added){
			date = _date;
			added = _added;
		}
	}
	
	/**
	 * Helps map a product to all its necessary data
	 */
	private class ProductData{
		public int currentSupply;
		public int currentMaxAge;
		public int itemsAdded;
		public int itemsRemoved;
		public int removedMaxAge;
		public int maxSupply;
		public int minSupply;
		public double averageSupply;
		public List<Integer> dailySupply;
		public List<Integer> removedAges;
		public double currentAvgAge;
		public double usedAvgAge;
		public Product _product;
		public PriorityQueue<ItemEvent> events;
		public Calendar startDate;
		
		public ProductData(Product product, Calendar _startDate){
			startDate = _startDate;
			currentSupply = 0;
			currentMaxAge = 0;
			itemsAdded = 0;
			itemsRemoved = 0;
			removedMaxAge = 0;
			maxSupply = 0;
			minSupply = 0;
			averageSupply = 0;
			usedAvgAge = 0;
			removedAges = new ArrayList<Integer>();
			dailySupply = new ArrayList<Integer>();
			_product = product;
			events = new PriorityQueue<ItemEvent>(10, new Comparator<ItemEvent>(){
								@Override
								public int compare(ItemEvent o1, ItemEvent o2) {
									return o1.date.compareTo(o2.date) * -1;
							}
			});
			

		}
		
		public void LoadCurrentSupply(Iterator<Item> items, Date endDate){
			int count = 0;
			List<Integer> currentAges = new ArrayList<Integer>();
			while(items.hasNext()){
				Item i = items.next();
				if (i.GetEntry().after(endDate))
					continue;
				int age = GetItemAge(i);
				if (age > currentMaxAge)
					currentMaxAge = age;
				currentAges.add(age);
				count++;
				
				if (!i.GetEntry().before(startDate.getTime())){
					itemsAdded++;
					events.add(new ItemEvent(i.GetEntry(), true));
				}
			}

			//find average age
			int average = 0;
			for (Integer i : currentAges){
				average += i;
			}

			//store results
			currentSupply = count;
			if (count == 0)
			{
				currentAvgAge = 0;
			}
			else
			{
				currentAvgAge = (double)average / (double)count;
			}
			dailySupply.add(0, count);
		}
	
		private int GetItemAge(Item i){
			
			Date endDate = new Date();
			
			if (i.GetExit() != null)
			{
				endDate = i.GetExit();
			}
			
			return (int)( (endDate.getTime() - i.GetEntry().getTime()) / (1000 * 60 * 60 * 24));
		}
		
		public void NewRemovedItem(Item i){
			itemsRemoved++;
			int age = GetItemAge(i);
			removedAges.add(age);
			if (age > removedMaxAge){
				removedMaxAge = age;
			}
			
			events.add(new ItemEvent(i.GetExit(), false));
			
			if (i.GetEntry().after(startDate.getTime()) || 
					i.GetEntry().equals(startDate.getTime())){
				itemsAdded++;
				events.add(new ItemEvent(i.GetEntry(), true));
			}
		}
		
		public void GetSupplyInfo()
		{
			double total = 0;
			int supply = this.currentSupply;
			this.maxSupply = supply;
			this.minSupply = supply;
			Iterator<ItemEvent> iter = events.iterator();
			Date temp = new Date();
			
			Date prodStartDate = this._product.GetEarliestItemEntry(
					HomeInventoryTracker.Instance().GetItemIndex());
			
			Date temp2 = startDate.getTime();
			if (prodStartDate.before(startDate.getTime()))
				prodStartDate = startDate.getTime();
			
			while (iter.hasNext())
			{
				ItemEvent event = iter.next();
				
				if (event.date.before(prodStartDate) || event.date.equals(prodStartDate))
					continue;
				
				double days = (double)((temp.getTime() - event.date.getTime()) 
											/ (1000.0 * 60 * 60 * 24));
				
				total += days * supply;
				supply += (event.added ? -1 : 1);
				temp = event.date;
				
				if (supply > maxSupply)
					maxSupply = supply;
				else if (supply  < minSupply)
					minSupply = supply;
			}
			
			double days = (double)((temp.getTime() - prodStartDate.getTime()) 
																/ (1000.0 * 60 * 60 * 24));
			total += days * supply;
			
			double totalDays = (double)((new Date().getTime() - prodStartDate.getTime())
																/ (1000.0 * 60 * 60 * 24));
			
			if (totalDays > 0)
			{
				this.averageSupply = total / totalDays;
			}
			else
			{
				this.averageSupply = supply;
			}
			
			//find average age of removed items
			int average = 0;
			for (Integer i : removedAges){
				average += i;
			}

			//store results
			if (removedAges.size() > 0)
				usedAvgAge = (double)average / (double)removedAges.size();
			else
				usedAvgAge = 0;
		}
	}
	
	//---------------------------------------------------------------
	//				End ProductData Class
	//---------------------------------------------------------------
	
	private PriorityQueue<Product> _products;
	private Map<Product, ProductData> pmap;
	private Calendar startDate;
	private Calendar endDate;
	private List<Product> products;
	private int months;
	
	public ProductStatisticsReport(int _months){
		months = _months;
		pmap = new HashMap<Product, ProductData>();
		products = new ArrayList<Product>();
		
		endDate = Calendar.getInstance();
		startDate = (Calendar)endDate.clone();
		startDate.add(Calendar.MONTH, -1 * months);
	}
	
	public ProductStatisticsReport(int _months, Calendar _endDate)
	{
		this.months = _months;
		pmap = new HashMap<Product, ProductData>();
		products = new ArrayList<Product>();
		
		endDate = Calendar.getInstance();
		endDate.clear();
		endDate.setTimeInMillis(_endDate.getTimeInMillis());
		
		startDate = (Calendar)endDate.clone();
		startDate.add(Calendar.MONTH, -1 * months);
	}
	
	public void LoadProducts(PriorityQueue<Product> products){
		_products = new PriorityQueue<Product>(products);
		while(!products.isEmpty()){
			Product p = products.poll();
			pmap.put(p, new ProductData(p, startDate));
		}
	}
	
	public void LoadCurrentSupply(Product p, Iterator<Item> items){
		pmap.get(p).LoadCurrentSupply(items, this.endDate.getTime());
	}
	
	public void NewRemovedItem(Item i){
		pmap.get(i.GetProduct()).NewRemovedItem(i);
	}

	@Override
	public void VisitProductIndex(ProductIndex index) {
		Iterator<Product> iter = 
			HomeInventoryTracker.Instance().GetProductIndex().GetProductIterator();
		PriorityQueue<Product> passin = new PriorityQueue<Product>(10, new Comparator<Product>(){
			@Override
			public int compare(Product o1, Product o2) {
				return o1.GetDescription().compareTo(o2.GetDescription());
			}

		});
		while(iter.hasNext()){
			Product p = iter.next();
			
			if (p.GetCreation().before(endDate.getTime()))
			{
				passin.add(p);
				products.add(p);
			}
		}
		LoadProducts(passin);
	}

	@Override
	public void VisitItemIndex(ItemIndex index) {
		for (Product p : products){
			LoadCurrentSupply(p, index.GetItemsByProduct(p));
		}
		Iterator<Item> removedItems = index.GetRemovedItemIterator();
		
		while (removedItems.hasNext()){
			Item i = removedItems.next();
			if (i.GetExit().before(startDate.getTime()))
			{
				continue;
			}
			
			NewRemovedItem(i);
		}
	}

	@Override
	public void BuildReport(ReportBuilder builder) {
		//might wanna use _products to incrementally extract products in the correct sorted order
		
		builder.setTitle("Product Report (" + Integer.toString(this.months) + " Months)");
		
		BuilderTable table = new BuilderTable();
		builder.AddObject(table);
		
		TableColumn descripCol = new TableColumn("Description");
		table.AddColumn(descripCol);
		
		TableColumn barcodeCol = new TableColumn("Barcode");
		table.AddColumn(barcodeCol);
		
		TableColumn sizeCol = new TableColumn("Size");
		table.AddColumn(sizeCol);
		
		TableColumn tMonthCol = new TableColumn("3-Month Supply");
		table.AddColumn(tMonthCol);
		
		TableColumn supCurCol = new TableColumn("Supply: Cur/Avg");
		table.AddColumn(supCurCol);
		
		TableColumn supMinMax = new TableColumn("Supply: Min/Max");
		table.AddColumn(supMinMax);
		
		TableColumn supUsedAdd = new TableColumn("Supply: Used/Added");
		table.AddColumn(supUsedAdd);
		
		TableColumn lifeCol = new TableColumn("Shelf Life");
		table.AddColumn(lifeCol);
		
		TableColumn usedAgeCol = new TableColumn("Used Age: Avg/Max");
		table.AddColumn(usedAgeCol);
		
		TableColumn curAgeCol = new TableColumn("Cur Age: Avg/Max");
		table.AddColumn(curAgeCol);
		
		DecimalFormat nf = new DecimalFormat("#.##");
		Iterator<Product> iter = _products.iterator();
		
		//sort the list of products by description
		List<Product> listOfProducts = new ArrayList<Product>();
		while (iter.hasNext()) {
			listOfProducts.add(iter.next());
		}
		Collections.sort(listOfProducts, new Comparator<Product>() {
				@Override
				public int compare(Product o1, Product o2) {
					return o1.GetDescription().compareTo(o2.GetDescription());
				}
		});
		
		//while (iter.hasNext())
		for (int i = 0; i < listOfProducts.size(); i++)
		{
			//Product product = iter.next();
			Product product = listOfProducts.get(i);
			
			ProductData pData = this.pmap.get(product);
			pData.GetSupplyInfo();
			
			TableRow row = new TableRow();
			table.AddRow(row);
			
			row.AddItem(descripCol, product.GetDescription());
			row.AddItem(barcodeCol, product.GetBarcode().GetValue());
			
			//handle the case that the size is an integer value
			if (product.GetSize().GetAmount() == ((int) product.GetSize().GetAmount())) {
				row.AddItem(sizeCol, (int) product.GetSize().GetAmount() + " " + 
						product.GetSize().GetType().toString());
			}
			else {
				row.AddItem(sizeCol, product.GetSize().toString());
			}
			
			int threeMonth = product.GetThreeMonth();
			row.AddItem(tMonthCol, (threeMonth > 0 ? Integer.toString(threeMonth) : ""));
			
			int shelfLife = product.GetShelfLife();
			row.AddItem(lifeCol, (shelfLife > 0 ? Integer.toString(shelfLife) + " months" : ""));
			
			String temp = Integer.toString(pData.currentSupply) + " / ";
			temp += nf.format(pData.averageSupply);
			row.AddItem(supCurCol, temp);
			
			temp = Integer.toString(pData.minSupply) + " / ";
			temp += Integer.toString(pData.maxSupply);
			row.AddItem(supMinMax, temp);
			
			temp = Integer.toString(pData.itemsRemoved) + " / ";
			temp += Integer.toString(pData.itemsAdded);
			row.AddItem(supUsedAdd, temp);
			
			temp = nf.format(pData.usedAvgAge) + " days / ";
			temp += Integer.toString(pData.removedMaxAge) + " days";
			row.AddItem(usedAgeCol, temp);
					
			temp = nf.format(pData.currentAvgAge) + " days / ";
			temp += Integer.toString(pData.currentMaxAge) + " days";
			row.AddItem(curAgeCol, temp);
		}
	}
	
	public Calendar getStartDate() {
		return startDate;
	}

	@Override
	public void VisitStorageUnit(StorageUnit unit) {
		throw new UnsupportedOperationException("Not applicable.");
	}

	@Override
	public void VisitHITCore(HomeInventoryTracker core) {
		throw new UnsupportedOperationException("Not applicable.");
	}

	@Override
	public void VisitProductGroup(ProductGroup group) {
		throw new UnsupportedOperationException("Not applicable.");
	}
	
	@Override
	public void VisitProductGroupPost(ProductGroup pg) {
		// Do Nothing.
	}

	@Override
	public void VisitItem(Item item) {/*do nothing*/}

	@Override
	public void VisitProduct(Product product) {/*do nothing*/}

}
